/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.google.cse.emea.universaltransformer.transformers;

import com.google.cse.emea.jct.utils.Profile;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.swing.JPanel;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import static java.util.logging.Level.*;
import static com.google.cse.emea.universaltransformer.TransformLogger.*;

/**
 *
 * @author jtrapp
 */
public abstract class AbstractXSLTModule implements TransformerModule {

    /** Parsed XML-Document */
    private Document document;
    Profile profile = null;
    String filename = null;
    private long lastModifiedInputFile;

    public AbstractXSLTModule() {

    }

    abstract public String getName();

    public boolean validate(String filename) {
        if ((document == null) || filename.equals(this.filename)) {
            this.filename = filename;
            parse(filename);
        }
        try {
            return validate(document);
        } catch (Exception e) {
            status("Validation Failed: " + e.getMessage());
            return false;
        }
    }

    public boolean validate(Document document) throws Exception {
        // create a SchemaFactory capable of understanding WXS schemas
        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//        doValidationForSchema("https://www.corp.google.com/eng/designdocs/aw_power_editor/ape-sharing-v1_3.xsd", factory, document);
//        doValidationForSchema("https://www.corp.google.com/eng/designdocs/aw_power_editor/ape-archive-v1_3.xsd", factory, document);
        String schema = profile.get(AbstractXSLTProfileEnum.INPUT_SCHEMA.getName());
        if (doValidationForSchema(schema, factory, document)) {
            status("Validation OK");
            return true;
        } else {
            status("Validation failed!");
            return false;
        }
    }

    
    /** Parse the xml file from the archive and generate a dom document for further use. 
     * 
     * @return
     */
    @Override
    public boolean parse(String filename) {
        this.filename = filename;
        File inputFile = new File(filename);
        String zipEntry = profile.get(AbstractXSLTProfileEnum.INPUT_ZIP_ENTRY);
        String encoding = profile.get(AbstractXSLTProfileEnum.INPUT_ENCODING);
        return parse(inputFile, zipEntry, encoding);
    }

    /** Parse the xml file from the archive and generate a dom document for further use. 
     * 
     * @return
     */
    
    public boolean parse(File inputFile, String zipEntry, String encoding) {
        InputStream stream = null;
        boolean retValue = false;

        try {
            if (zipEntry != null && zipEntry.length() > 0) {
                ZipFile zFile = new ZipFile(inputFile);
                stream = zFile.getInputStream(zFile.getEntry(zipEntry));
            } else {
                stream = new FileInputStream(inputFile);
                log(INFO, "Input File size: "+inputFile.length());
            }

            InputSource source = new InputSource();
            Charset charset = getCharset(encoding);
            source.setEncoding(charset.displayName());
            source.setCharacterStream(new InputStreamReader(stream, charset));
//            java.io.Reader r = source.getCharacterStream();
//            char[] c = new char[10];
//            if (r.read(c)>0){
//                log(INFO, "Read: "+new String(c));
//            }else{
//                log(SEVERE, "Failed to read from stream");
//            }
//            stream.reset();
            
            // parse an XML document into a DOM tree
            DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            document = (Document) parser.parse(source);

            retValue = true;
        } catch (Throwable e) {
            log(SEVERE, "Error " + e.getMessage(), e);
            e.printStackTrace();
            clearDocument();
        } finally {
            try {
                stream.close();
            } catch (IOException ioe) { //do nothing}
            }

            return retValue;
        }

    }

    public boolean transform(String outputFilename, java.util.AbstractMap<String, String> values) {
        try {

            Charset charset = getCharset(profile.get(AbstractXSLTProfileEnum.OUTPUT_ENCODING));

            OutputStream stream = null;

            String zipEntry = profile.get(AbstractXSLTProfileEnum.OUTPUT_ZIP_ENTRY);
            if (zipEntry != null && zipEntry.length() > 0) {
                status("Writing compressed file ");
                stream = new ZipOutputStream(new FileOutputStream(outputFilename));
                ((ZipOutputStream) stream).putNextEntry(new ZipEntry(profile.get(AbstractXSLTProfileEnum.OUTPUT_ZIP_ENTRY)));
            } else {
                stream = new FileOutputStream(outputFilename);
            }


            StreamResult result = new StreamResult(new OutputStreamWriter(stream, charset));
            javax.xml.transform.dom.DOMSource domsource = new javax.xml.transform.dom.DOMSource(document);
            // Use a Transformer for output
            TransformerFactory tFactory = TransformerFactory.newInstance();
            String stylesheet = profile.get(AbstractXSLTProfileEnum.TRANSFORM_STYLESHEET);
            log(INFO, "Stylesheet for transformation: " + stylesheet);
            Transformer transformer = tFactory.newTransformer(getStreamSource(stylesheet));
            if (values != null) {
                for (String key : values.keySet()) {
                    transformer.setParameter(key, values.get(key));
                }
            }
            transformer.transform(domsource, result);

            if (stream instanceof ZipOutputStream) {
                ((ZipOutputStream) stream).closeEntry();
            }
            stream.close();
            status("Finished");
            return true;
        } catch (Exception e) {
            log(SEVERE, "Failed!" + e.getMessage(), e);
        }
        return false;
    }

    /** Check if the input file has changed.*/
    public boolean checkChanged(String filename) {
        if ((this.filename == null) || (!filename.equals(this.filename))) {
            this.filename = filename;
            lastModifiedInputFile = 0;
        }
        long changed = new File(filename).lastModified();
        if (changed != lastModifiedInputFile) {

            lastModifiedInputFile = changed;
            return true;
        }
        return false;
    }

    public void clearDocument() {
        document = null;
    }

    /**Do the validation */
    private boolean doValidationForSchema(String schemaURL, SchemaFactory factory, Document document) throws SAXException, IOException {
        log(INFO, "Checking Schema from URL: " + schemaURL);
        // load a WXS schema, represented by a Schema instance
        javax.xml.transform.Source schemaFile = new javax.xml.transform.stream.StreamSource(new InputStreamReader(new URL(schemaURL).openConnection().getInputStream()));
        javax.xml.validation.Schema schema = factory.newSchema(schemaFile);

        // create a Validator instance, which can be used to validate an instance document
        javax.xml.validation.Validator validator = schema.newValidator();


        // validate the DOM tree
        try {
            validator.validate(new javax.xml.transform.dom.DOMSource(document));
            return true;
        } catch (org.xml.sax.SAXException e) {
            status("Invalid Document. " + " " + e.getMessage());
            log(INFO, "Invalid Document. " + " " + e.getMessage());
            return false;
        }
    }

    /**Do the validation */
    private boolean doValidationForSchema(File file, SchemaFactory factory, Document document) throws SAXException, IOException {
        log(INFO, "Checking Schema: " + file.getAbsolutePath() + " Size:" + file.length());
        // load a WXS schema, represented by a Schema instance
        javax.xml.transform.Source schemaFile = new javax.xml.transform.stream.StreamSource(file);
        javax.xml.validation.Schema schema = factory.newSchema(schemaFile);

        // create a Validator instance, which can be used to validate an instance document
        javax.xml.validation.Validator validator = schema.newValidator();
        log(INFO, "Schema: " + (schema).toString() + " ");
        // validate the DOM tree
        try {
            validator.validate(new javax.xml.transform.dom.DOMSource(document));
            return true;
        } catch (org.xml.sax.SAXException e) {
            status("Invalid Document. " + " " + e.getMessage());
            log(INFO, "Invalid Document. " + " " + e.getMessage());
            return false;
        }
    }

    public StreamSource getStreamSource(String stylesheetURL) throws IOException {
        return new StreamSource(new URL(stylesheetURL).openStream());
    }

    abstract public JPanel getInputPanel();

    abstract public JPanel getOutputPanel();


    public void setProfile(Profile profile) {
        this.profile = profile;
        String inputFilename = profile.get(CSVProfileEnum.INPUT_FILE);
        if (inputFilename == null || !inputFilename.equals(filename)) {
            document = null;
            filename = inputFilename;
        }
    }

    /** Get a charset for name.
     */
    public static Charset getCharset(String name) {
        try {
            return Charset.forName(name);
        } catch (UnsupportedCharsetException e) {
            log(WARNING, "Unsupported charset " + name + ". Using UTF-8.");
            return Charset.forName("UTF-8");
        }
    }

    public boolean hasDocument() {
        return document != null;
    }
}
